#include "stdafx.h"

#include "lexer.h"
#include <ctype.h>

namespace funpl
{

namespace parser
{

/// True, if the given character makes a valid beginning for an identifier.
inline bool is_identifier_begin(int c)
{
	return isalpha(c) || c == '_';
}
/// True, if the given character is part of a valid identifier.
inline bool is_identifier(int c)
{
	return isalnum(c) || c == '_';
}

/// True, if the given character makes a valid beginning for a numeric literal.
inline bool is_num_literal_begin(int c)
{
	return isdigit(c) != 0;
}
/// True, if the given character is part of a valid numeric literal.
inline bool is_num_literal(int c)
{
	return isalnum(c) || c == '.';
}

/// True, if the given character is part of a special character sequence.
inline bool is_special(int c)
{
	return ispunct(c) && c != '_';
}

/// Counts the number of backslashes starting at the given position, checking backwards.
template <class Iterator>
inline size_t count_backslashes(Iterator it)
{
	size_t count = 0;

	while (*it == '\\')
	{
		++count;
		--it;
	}

	return count;
}

/// Extracts the identifier stating at the given position.
token extract_identifier(const char* &nextChar)
{
	token token;

	const char *identifierBegin = nextChar++;

	while (is_identifier(*nextChar))
		++nextChar;

	token.range.assign(identifierBegin, nextChar);
	token.type = identifier_token_type(token.range);

	return token;
}

/// Extracts the numeric literal stating at the given position.
token extract_num_literal(const char* &nextChar)
{
	token token;
	
	const char *numericBegin = nextChar++;

	while (is_num_literal(*nextChar))
		++nextChar;

	token.range.assign(numericBegin, nextChar);
	token.type = token_type::tok_num_literal;

	return token;
}

/// Extracts the character literal stating at the given position.
token extract_char_literal(const char* &nextChar)
{
	token token;
	
	const char *numericBegin = nextChar++;

	while (isprint(*nextChar) && (*nextChar != '\'' || count_backslashes(std::prev(nextChar)) % 2 == 1))
		++nextChar;

	// Don't forget to extract terminating character
	if (*nextChar)
		++nextChar;

	token.range.assign(numericBegin, nextChar);
	token.type = token_type::tok_char_literal;

	return token;
}

/// Extracts the character literal stating at the given position.
token extract_string_literal(const char* &nextChar)
{
	token token;
	
	const char *numericBegin = nextChar++;

	while (isprint(*nextChar) && (*nextChar != '"' || count_backslashes(std::prev(nextChar)) % 2 == 1))
		++nextChar;

	// Don't forget to extract terminating character
	if (*nextChar)
		++nextChar;

	token.range.assign(numericBegin, nextChar);
	token.type = token_type::tok_string_literal;

	return token;
}

/// Tries to match the given special character sequence.
token match_special_token(util::const_char_range &range)
{
	token token;

	// Currently no operators longer than two characters

	// ORDER: Check longest operators first
	if (token.type == token_type::unknown && range.size() >= 2)
	{
		// Trim range to length of 2 (output parameter, skipped afterwards!)
		range.end() = range.begin() + 2;

		token.type = special_2_token_type(range);

		if (token.type != token_type::unknown)
			token.range = range;
	}

	if (token.type == token_type::unknown && range.size() >= 1)
	{
		// Trim range to length of 1 (output parameter, skipped afterwards!)
		range.end() = range.begin() + 1;

		token.type = special_1_token_type(range);

		if (token.type != token_type::unknown)
			token.range = range;
	}

	return token;
}

/// Extracts the special character sequence starting at the given position.
token extract_special(const char* &nextChar)
{
	token token;
	
	util::const_char_range specialRange(nextChar, nextChar + 1);

	// Extract maximum character range
	while (is_special(*specialRange.end()))
		++specialRange.end();

	token = match_special_token(specialRange);

	// Put unused special characters back (always skips at least one character!)
	nextChar = specialRange.end();

	return token;
}

/// Skips the comment starting at the given position.
void skip_line_comment(const char* &nextChar)
{
	while (*++nextChar && *nextChar != '\n');
}

/// Skips the comment starting at the given position.
void skip_multiline_comment(const char* &nextChar)
{
	while (*++nextChar && (*nextChar != '/' || *std::prev(nextChar) != '*'));

	// Don't forget to extract terminating character
	if (*nextChar)
		++nextChar;
}

/// Gets the next token.
token next_token(const char* &nextChar, bool breakToSemicolon)
{
	token token;

	bool skipChars = true;

	// Continue skipping until neither white spaces nor comments
	while (skipChars)
	{
		// Skip whitespaces (but treat new-lines as semicolons)
		while (isspace(*nextChar) && (!breakToSemicolon || *nextChar != '\n'))
			++nextChar;

		skipChars = false;

		// Skip comments
		if (*nextChar == '/')
		{
			if (*std::next(nextChar) == '/')
			{
				skip_line_comment(nextChar);
				skipChars = true;
			}
			else if (*std::next(nextChar) == '*')
			{
				skip_multiline_comment(nextChar);
				skipChars = true;
			}
		}
	}

	if (*nextChar)
	{
		// Treat new-lines as semicolons
		if (breakToSemicolon && *nextChar == '\n')
		{
			token.type = token_type::tok_semicolon;
			++nextChar;
		}
		// Pass on to specific token lexers
		else if (is_identifier_begin(*nextChar))
			token = extract_identifier(nextChar);
		else if (is_num_literal_begin(*nextChar))
			token = extract_num_literal(nextChar);
		else if (*nextChar == '\'')
			token = extract_char_literal(nextChar);
		else if (*nextChar == '"')
			token = extract_string_literal(nextChar);
		else
			token = extract_special(nextChar);
	}
	else
		// Null character indicates end of file
		token.type = token_type::end;

	return token;
}

} // namespace

} // namespace